﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Collections.Generic;
using System.Windows.Media;

using Nova.CodeDOM;

namespace Nova.UI
{
    /// <summary>
    /// The view model for a <see cref="CodeDOM.TypeRef"/>.
    /// </summary>
    public class TypeRefVM : TypeRefBaseVM
    {
        #region /* STATICS */

        internal static void AddViewModelMapping()
        {
            CreateViewModel.Add(typeof(TypeRef),
                delegate(CodeObject codeObject, bool isDescription, Dictionary<CodeObject, CodeObjectVM> dictionary) { return new TypeRefVM((TypeRef)codeObject, true, dictionary); });
        }

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a view model instance for the specified <see cref="CodeDOM.TypeRef"/>.
        /// </summary>
        public TypeRefVM(TypeRef typeRef, bool createTypeArgumentVMs, Dictionary<CodeObject, CodeObjectVM> dictionary)
            : base(typeRef, createTypeArgumentVMs, dictionary)
        { }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The underlying <see cref="CodeDOM.TypeRef"/> model.
        /// </summary>
        public TypeRef TypeRef
        {
            get { return (TypeRef)CodeObject; }
        }

        #endregion

        #region /* METHODS */

        #endregion

        #region /* RENDERING */

        protected override Brush RenderBrush(RenderFlags flags)
        {
            return TYPE_BRUSH;
        }

        public override void RenderExpression(CodeRenderer renderer, RenderFlags flags)
        {
            RenderType(renderer, _typeArgumentVMs, flags, this);
        }

        public void RenderType(CodeRenderer renderer, List<ExpressionVM> typeArgumentVMs, RenderFlags flags, CodeObjectVM tag)
        {
            // If it's a nested type, and we have no dot prefix, and the ShowParentTypes flag is set, then
            // render all parent types with appropriate type arguments (this shouldn't occur in display of
            // code, but only when displaying an evaluated type reference, such as in a tooltip).
            // Nova will include all type arguments for any parent types in a reference to a nested type
            // (as .NET Reflection also does).  Such parent type arguments are ignored for display purposes
            // if the TypeRef is displayed in code that includes parent type prefixes or is within the scope
            // of the parent generic type.
            // Also do this for GenericParameters (which are treated as 'nested' by reflection).
            if (TypeRef.IsNested || TypeRef.IsGenericParameter)
            {
                TypeRefBase typeRefBase = TypeRef.GetDeclaringType();
                if (typeRefBase != null)
                {
                    // Recursively render the parent type minus the type arguments that belong to the current type
                    List<ExpressionVM> parentTypeArgumentVMs = null;
                    if (typeArgumentVMs != null)
                    {
                        int localCount = TypeRef.GetLocalTypeArgumentCount();
                        int parentCount = typeArgumentVMs.Count - localCount;
                        if (parentCount > 0)
                        {
                            if (localCount == 0)
                            {
                                parentTypeArgumentVMs = typeArgumentVMs;
                                typeArgumentVMs = null;
                            }
                            else
                            {
                                parentTypeArgumentVMs = typeArgumentVMs.GetRange(0, parentCount);
                                typeArgumentVMs = typeArgumentVMs.GetRange(parentCount, localCount);
                            }
                        }
                    }

                    // We must always extract the parent type arguments, but only render the parent types if appropriate
                    if (!flags.HasFlag(RenderFlags.HasDotPrefix) && flags.HasFlag(RenderFlags.ShowParentTypes) && typeRefBase is TypeRef)
                    {
                        // If we're about to render an enclosing type without type parameters, then use the declared
                        // ones by default.  This is necessary for nested Enums, since they are never considered to
                        // be generic types, and won't have any type arguments even if nested inside generic types.
                        // Since an Enum can't be instantiated, the declared type arguments are always appropriate.
                        if (parentTypeArgumentVMs == null || parentTypeArgumentVMs.Count == 0)
                            parentTypeArgumentVMs = CreateListVM<Expression, ExpressionVM>(((TypeRef)typeRefBase).GetTypeParametersAsArguments());

                        TypeRefVM typeRefVM = (TypeRefVM)CreateVM(typeRefBase);
                        typeRefVM.RenderType(renderer, parentTypeArgumentVMs, flags, typeRefVM);
                        renderer.RenderText(Dot.ParseToken, PUNC_BRUSH, null);
                        flags |= RenderFlags.HasDotPrefix;
                    }
                }
            }

            RenderFlags passFlags = flags & ~RenderFlags.Description;
            object reference = TypeRef.GetReferencedType();

            // Handle references to Types
            if (reference is Type)
            {
                Type type = (Type)reference;

                // If we have array ranks, or were requested to suppress them (by NewArray for jagged
                // arrays), then suppress them in the Type rendering by getting the innermost type.
                if (TypeRef.HasArrayRanks || flags.HasFlag(RenderFlags.SuppressBrackets))
                {
                    while (type.IsArray)
                        type = type.GetElementType();
                }

                // If we have type arguments, override any in the Type itself
                if (typeArgumentVMs != null)
                {
                    // Render "Nullable<Type>" as "Type?" if optimizations is on *and* there's no "System." prefix
                    if (TypeRef.IsNullableType && !flags.HasFlag(RenderFlags.HasDotPrefix))
                    {
                        // Render the Nullable type argument
                        ExpressionVM typeArgumentVM = typeArgumentVMs[0];
                        typeArgumentVM.Render(renderer, passFlags & ~RenderFlags.ShowParentTypes);

                        // Render the '?' in the appropriate color
                        typeArgumentVM = typeArgumentVM.SkipPrefixes();
                        Brush brush = TYPE_BRUSH;
                        if (typeArgumentVM is TypeParameterRefVM)
                            brush = IDENTIFIER_BRUSH;
                        else if (typeArgumentVM is TypeRefVM)
                        {
                            if (((TypeRefVM)typeArgumentVM).TypeRef.IsBuiltInType)
                                brush = KEYWORD_BRUSH;
                        }
                        else if (typeArgumentVM is UnresolvedRefVM)
                            brush = ERROR_BRUSH;
                        // Render with a null object tag for now (no special tooltip handling)
                        renderer.RenderText(TypeRefBase.ParseTokenNullable, brush, null);
                    }
                    else
                    {
                        RenderType(renderer, type, passFlags | RenderFlags.SuppressTypeArgs, tag);
                        RenderTypeArguments(renderer, typeArgumentVMs, flags, tag);
                    }
                }
                else
                {
                    // If the TypeRef has no type arguments, we still want to suppress any on the
                    // type itself - this is valid for bad (incomplete) code.
                    RenderType(renderer, type, passFlags | RenderFlags.SuppressTypeArgs, tag);
                }
            }
            else  // Handle references to TypeDecls, TypeParameters
            {
                renderer.RenderName(TypeRef.Name, RenderBrush(flags), tag, flags);
                RenderTypeArguments(renderer, typeArgumentVMs, flags, tag);
            }

            // Render any array ranks last
            RenderArrayRanks(renderer, passFlags);
        }

        #endregion
    }
}
